package cn.xeblog.plugin.game.sudoku.algorithm;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;

/**
 * 这个类表示一个数独Grid，由一个9x9矩阵组成，其中包含9个3x3{@link Cell} <br/><br/>
 * <a href="https://github.com/a11n/sudoku">来源</a>
 */
public class Grid {

  /**
   * 网格，由二维单元格数组组成
   */
  private final Cell[][] grid;

  /**
   * 构造
   *
   * @param grid 二维网格
   */
  private Grid(Cell[][] grid) {
    this.grid = grid;
  }

  /**
   * 一个静态工厂方法，它返回给定二维整数数组的Grid
   *
   * @param grid 网格的二维整数数组表示
   * @return 二维整数数组的Grid实例
   */
  public static Grid of(int[][] grid) {
    // 验证网格
    verifyGrid(grid);

    Cell[][] cells = new Cell[9][9];
    List<List<Cell>> rows = new ArrayList<>();
    List<List<Cell>> columns = new ArrayList<>();
    List<List<Cell>> boxes = new ArrayList<>();

    // 初始化行、列、九宫格框
    for (int i = 0; i < 9; i++) {
      rows.add(new ArrayList<>());
      columns.add(new ArrayList<>());
      boxes.add(new ArrayList<>());
    }

    Cell lastCell = null;
    // 遍历网格，将二维数组转换为单元格对象
    for (int row = 0; row < grid.length; row++) {
      for (int column = 0; column < grid[row].length; column++) {

        Cell cell = new Cell(grid[row][column]);
        cells[row][column] = cell;

        rows.get(row).add(cell);
        columns.get(column).add(cell);
        boxes.get((row / 3) * 3 + column / 3).add(cell);

        if (lastCell != null) {
          lastCell.setNextCell(cell);
        }

        lastCell = cell;
      }
    }
    // 设置指定单元格同一行的所有其他单元格（不包含自己）
    for (int i = 0; i < 9; i++) {
      List<Cell> row = rows.get(i);
      for (Cell cell : row) {
        List<Cell> rowNeighbors = new ArrayList<>(row);
        rowNeighbors.remove(cell);

        cell.setRowNeighbors(rowNeighbors);
      }
      // 设置指定单元格同一列中的所有其他单元格（不包含自己）
      List<Cell> column = columns.get(i);
      for (Cell cell : column) {
        List<Cell> columnNeighbors = new ArrayList<>(column);
        columnNeighbors.remove(cell);

        cell.setColumnNeighbors(columnNeighbors);
      }

      // 设置指定单元格相同框中的所有其他单元格（不包含自己）
      List<Cell> box = boxes.get(i);
      for (Cell cell : box) {
        List<Cell> boxNeighbors = new ArrayList<>(box);
        boxNeighbors.remove(cell);

        cell.setBoxNeighbors(boxNeighbors);
      }
    }
    return new Grid(cells);
  }

  /**
   * 生成空网格
   *
   * @return 一个空网格
   */
  public static Grid emptyGrid() {
    int[][] emptyGrid = new int[9][9];
    return Grid.of(emptyGrid);
  }

  /**
   * 验证网格
   *
   * @param grid 待验证的网格（二维数组）
   */
  private static void verifyGrid(int[][] grid) {
    // 非空
    if (grid == null) {
      throw new IllegalArgumentException("grid must not be null");
    }

    // 网格必须有九行
    if (grid.length != 9) {
      throw new IllegalArgumentException("grid must have nine rows");
    }

    for (int[] row : grid) {
      // 必须有九行
      if (row.length != 9) {
        throw new IllegalArgumentException("grid must have nine columns");
      }

      // 值必须0~9
      for (int value : row) {
        if (value < 0 || value > 9) {
          throw new IllegalArgumentException("grid must contain values from 0-9");
        }
      }
    }
  }

  /**
   * 返回此网格的大小。如果希望遍历所有{@link Cell}，则此方法非常有用。<br><br>要访问一个单元格，使用{@link #getCell(int, int)}。
   * <br><br>注:这是一维的尺寸。此网格包含大小为x的{@link Cell}。
   *
   * @return 当前网格的大小
   */
  public int getSize() {
    return grid.length;
  }

  /**
   * 返回网格中给定位置的{@link Cell}。<br><br>这个Grid有0到{@link #getSize()}的行和0到{@link #getSize()}的列。
   *
   * @param row    {@link Cell}属性的行
   * @param column {@link Cell}属性的列
   * @return 给定位置的{@link Cell}
   */
  public Cell getCell(int row, int column) {
    return grid[row][column];
  }

  /**
   * 检查给定值是否对特定{@link Cell}有效br><br>如果值在同一行、列和框中不存在，则该值是有效的
   *
   * @param cell  要检查的{@link Cell}
   * @param value 要验证的值
   * @return 如果给定值有效则为True，否则为false
   */
  public boolean isValidValueForCell(Cell cell, int value) {
    return isValidInRow(cell, value) && isValidInColumn(cell, value) && isValidInBox(cell, value);
  }

  /**
   * 单元格同行的所有其他单元格的集合是否不包含当前值
   *
   * @param cell  单元格
   * @param value 待比较的值
   * @return true, 单元格同行的所有其他单元格的集合不包含当前值, 否则，false
   */
  private boolean isValidInRow(Cell cell, int value) {
    return !getRowValuesOf(cell).contains(value);
  }

  /**
   * 单元格在同一列中的所有其他单元格的集合是否不包含当前值
   *
   * @param cell  单元格
   * @param value 待比较的值
   * @return true, 单元格在同一列中的所有其他单元格的集合不包含当前值, 否则，false
   */
  private boolean isValidInColumn(Cell cell, int value) {
    return !getColumnValuesOf(cell).contains(value);
  }

  /**
   * 单元格相同框中的所有其他单元格的集合是否不包含当前值
   *
   * @param cell  单元格
   * @param value 待比较的值
   * @return true, 单元格相同框中的所有其他单元格的集合不包含当前值, 否则，false
   */
  private boolean isValidInBox(Cell cell, int value) {
    return !getBoxValuesOf(cell).contains(value);
  }

  /**
   * 获取指定单元格同行的所有其他单元格的集合（不包含自己）
   *
   * @param cell 指定的单元格
   * @return 指定单元格同行的所有其他单元格的集合（不包含自己）
   */
  private Collection<Integer> getRowValuesOf(Cell cell) {
    return cell.getRowNeighbors().stream().map(Cell::getValue).collect(Collectors.toList());
  }

  /**
   * 获取指定单元格在同一列中的所有其他单元格的集合（不包含自己）
   *
   * @param cell 指定的单元格
   * @return 指定单元格在同一列中的所有其他单元格的集合（不包含自己）
   */
  private Collection<Integer> getColumnValuesOf(Cell cell) {
    return cell.getColumnNeighbors().stream().map(Cell::getValue).collect(Collectors.toList());
  }

  /**
   * 获取指定单元格相同框中的所有其他单元格的集合（不包含自己）
   *
   * @param cell 指定的单元格
   * @return 指定单元格相同框中的所有其他单元格的集合
   */
  private Collection<Integer> getBoxValuesOf(Cell cell) {
    return cell.getBoxNeighbors().stream().map(Cell::getValue).collect(Collectors.toList());
  }

  /**
   * 返回此网格的第一个空{@link Cell}。<br><br>注意:结果由{@link Optional}包装。
   *
   * @return 一个非空值，如果存在，则包含第一个空{@link Cell}
   */
  public Optional<Cell> getFirstEmptyCell() {
    Cell firstCell = grid[0][0];
    if (firstCell.isEmpty()) {
      return Optional.of(firstCell);
    }

    return getNextEmptyCellOf(firstCell);
  }

  /**
   * 返回下一个空{@link Cell}连续给定的网格{@link Cell}。<br><br>注意:结果被一个{@link Optional}包装。
   *
   * @param cell 应该获得其下一个空{@link Cell}的{@link Cell}
   * @return 一个非空值，如果存在则包含下一个空{@link Cell}
   */
  public Optional<Cell> getNextEmptyCellOf(Cell cell) {
    Cell nextEmptyCell = null;

    // 不停的获取下一个单元格，直至获取到的下一单元格为空时跳出
    while ((cell = cell.getNextCell()) != null) {
      if (!cell.isEmpty()) {
        continue;
      }

      nextEmptyCell = cell;
      break;
    }

    return Optional.ofNullable(nextEmptyCell);
  }

  /**
   * 返回此网格的字符串表示形式。
   *
   * @return 此网格的字符串表示形式。
   */
  @Override
  public String toString() {
    return StringConverter.toString(this);
  }

  /**
   * Grid转成二维数组
   *
   * @author ☆程序员鼓励师☆
   * @date 2022/8/12 13:21
   */
  public int[][] toArray() {
    int size = this.getSize();
    int[][] result = new int[size][size];
    // 遍历网格，并将单元格的值设置至二维数组中
    for (int row = 0; row < size; row++) {
      for (int column = 0; column < size; column++) {
        result[row][column] = this.getCell(row, column).getValue();
      }
    }
    return result;
  }

  /**
   * 数独网格中的单元格
   */
  @Getter
  @Setter
  public static class Cell {

    /**
     * 单元格当前值
     */
    private int value;
    /**
     * 此单元格同行的所有其他单元格的集合（不包含自己）
     */
    private Collection<Cell> rowNeighbors;
    /**
     * 此单元格在同一列中的所有其他单元格的集合（不包含自己）
     */
    private Collection<Cell> columnNeighbors;
    /**
     * 此单元格相同框中的所有其他单元格的集合（不包含自己）
     */
    private Collection<Cell> boxNeighbors;
    /**
     * 下一个单元格
     */
    private Cell nextCell;

    /**
     * 构造
     *
     * @param value 单元格的值
     */
    public Cell(int value) {
      this.value = value;
    }


    /**
     * 单元格是否为空.
     *
     * @return 单元格为空，则为true，否则为false
     */
    public boolean isEmpty() {
      return value == 0;
    }
  }

  /**
   * 字符串转换器
   */
  private static class StringConverter {

    /**
     * 将网格转化成字符串
     *
     * @param grid 网格
     * @return 将网格转化后的字符串
     */
    public static String toString(Grid grid) {
      StringBuilder builder = new StringBuilder();
      int size = grid.getSize();

      // 打印顶部边框
      printTopBorder(builder);
      for (int row = 0; row < size; row++) {
        // 打印行边框
        printRowBorder(builder);
        for (int column = 0; column < size; column++) {
          // 打印值
          printValue(builder, grid, row, column);
          // 打印右边行边框
          printRightColumnBorder(builder, column + 1, size);
        }
        // 打印行边框
        printRowBorder(builder);
        // 换行
        builder.append("\n");
        // 打印底部行边框
        printBottomRowBorder(builder, row + 1, size);
      }
      // 打印底部边框
      printBottomBorder(builder);

      return builder.toString();
    }

    /**
     * 打印顶部边框
     *
     * @param builder 字符串构建器
     */
    private static void printTopBorder(StringBuilder builder) {
      builder.append("╔═══╤═══╤═══╦═══╤═══╤═══╦═══╤═══╤═══╗\n");
    }

    /**
     * 打印行边框
     *
     * @param builder 字符串构建器
     */
    private static void printRowBorder(StringBuilder builder) {
      builder.append("║");
    }

    /**
     * 打印值
     *
     * @param builder 字符串构建器
     * @param grid    网格
     * @param row     行
     * @param column  列
     */
    private static void printValue(StringBuilder builder, Grid grid, int row, int column) {
      int value = grid.getCell(row, column).getValue();
      String output = value != 0 ? String.valueOf(value) : " ";
      builder.append(" " + output + " ");
    }

    /**
     * 打印右边列边框
     *
     * @param builder 字符串构建器
     * @param column  列
     * @param size    大小
     */
    private static void printRightColumnBorder(StringBuilder builder, int column, int size) {
      // 超界，跳过
      if (column == size) {
        return;
      }

      if (column % 3 == 0) {
        builder.append("║");
      } else {
        builder.append("│");
      }
    }

    /**
     * 打印底部行边框
     *
     * @param builder 字符串构建器
     * @param row     行
     * @param size    大小
     */
    private static void printBottomRowBorder(StringBuilder builder, int row, int size) {
      // 超界，跳过
      if (row == size) {
        return;
      }

      if (row % 3 == 0) {
        builder.append("╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n");
      } else {
        builder.append("╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n");
      }
    }

    /**
     * 打印底部边框
     *
     * @param builder 字符串构建器
     */
    private static void printBottomBorder(StringBuilder builder) {
      builder.append("╚═══╧═══╧═══╩═══╧═══╧═══╩═══╧═══╧═══╝\n");
    }
  }
}
